﻿using System;
using System.Text.RegularExpressions;
//the following TFS server references can be found under %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\TFSJobAgent
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.VersionControl.Server;

namespace TFSSubscribers
{
    public class BranchingPolicy : ISubscriber
    {
        #region regular expressions

        private static readonly Regex TEAM_BUILD_TYPES = new Regex(@"^\$/[^/\\]+/TeamBuildTypes/.*", RegexOptions.IgnoreCase);
        private static readonly Regex GROUPING_REGEX = new Regex(@"^\$(/.+?/)(((Development|Staging|Production)/.+?)|(Integration))($|/.*)", RegexOptions.IgnoreCase);

        #endregion

        public string Name
        {
            get { return "Branch Policy"; }
        }

        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Normal; }
        }

        //When debuging, copy .dlls and .pdb 
        //to %Program Files%\Microsoft Team Foundation Server 2010\Application Tier\Web Services\bin\Plugins
        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext,
            NotificationType notificationType,
            object notificationEventArgs,
            out int statusCode,
            out string statusMessage,
            out ExceptionPropertyCollection properties)
        {
            EventNotificationStatus response = EventNotificationStatus.ActionApproved;
            properties = new ExceptionPropertyCollection();
            statusMessage = String.Empty;
            statusCode = 0;

            // If this is a Check-in notification and is a decision point
            if (notificationEventArgs is CheckinNotification && notificationType == NotificationType.DecisionPoint)
            {
                CheckinNotification args = notificationEventArgs as CheckinNotification;
                if (args.CheckinType == CheckinType.Branch)
                {
                    string sourceBranch = requestContext.Method.Parameters["sourcePath"];
                    if (!TEAM_BUILD_TYPES.IsMatch(sourceBranch))
                    {
                        string targetBranch = requestContext.Method.Parameters["targetPath"];
                        statusMessage = EvaluateBranch(sourceBranch, targetBranch);
                        if (!string.IsNullOrEmpty(statusMessage))
                        {
                            properties.Set("sourceBranch", sourceBranch);
                            properties.Set("targetBranch", targetBranch);
                            response = EventNotificationStatus.ActionDenied;
                        }
                    }
                }
            }
            return response;
        }

        public Type[] SubscribedTypes()
        {
            return new Type[] { typeof(CheckinNotification) };
        }



        /// <summary>
        /// evaluates if the source and target of the branching are valid
        /// </summary>
        /// <param name="sourceString">the source of the branch</param>
        /// <param name="targetString">where the branch is branching to</param>
        /// <returns></returns>
        private static string EvaluateBranch(string sourceString, string targetString)
        {
            string failure = string.Empty;
            Match source = GROUPING_REGEX.Match(sourceString);
            Match target = GROUPING_REGEX.Match(targetString);

            //only check the project and path if the regex matched, if either did not match one of the branch area checks will give give a better description of the problem
            if (target.Success && source.Success)
            {
                //if the team projects do not match, raise a policy failure
                if (!source.Groups[1].Value.Equals(target.Groups[1].Value, StringComparison.InvariantCultureIgnoreCase))
                {
                    failure = string.Format("Cannot branch from project {0} to project {1}", source.Groups[1].Value, target.Groups[1].Value);
                }
            }

            //for both the target and the source branches, populate with group 4, which is development, staging, or production, but if empty, populate with group 5 which is integration
            string sourceBranch = string.IsNullOrEmpty(source.Groups[4].Value) ? source.Groups[5].Value : source.Groups[4].Value;
            string targetBranch = string.IsNullOrEmpty(target.Groups[4].Value) ? target.Groups[5].Value : target.Groups[4].Value;

            //if the branch is to development, it must be from integration
            if (targetBranch.Equals("DEVELOPMENT", StringComparison.InvariantCultureIgnoreCase))
            {
                if (!sourceBranch.Equals("INTEGRATION", StringComparison.InvariantCultureIgnoreCase))
                {
                    failure = string.Format("Cannot branch from {0} to {1}", sourceString, targetString);
                }
            }
            //if the branch is to integration, it must be from development
            else if (targetBranch.Equals("INTEGRATION", StringComparison.InvariantCultureIgnoreCase))
            {
                if (!sourceBranch.Equals("DEVELOPMENT", StringComparison.InvariantCultureIgnoreCase))
                {
                    failure = string.Format("Cannot branch from {0} to {1}", sourceString, targetString);
                }
            }
            //if the branch is to staging, it must be from development
            else if (targetBranch.Equals("STAGING", StringComparison.InvariantCultureIgnoreCase))
            {
                if (!sourceBranch.Equals("DEVELOPMENT", StringComparison.InvariantCultureIgnoreCase))
                {
                    failure = string.Format("Cannot branch from {0} to {1}", sourceString, targetString);
                }
            }
            //if the branch is to production, it must be from staging
            else if (targetBranch.Equals("PRODUCTION", StringComparison.InvariantCultureIgnoreCase))
            {
                if (!sourceBranch.Equals("STAGING", StringComparison.InvariantCultureIgnoreCase))
                {
                    failure = string.Format("Cannot branch from {0} to {1}", sourceString, targetString);
                }
            }
            //must be to integration, development, staging, or production
            else
            {
                failure = string.Format("Branches are only allowed to be created in integration, development, staging, and production, NOT {0}", targetString);
            }

            return failure;
        }
    }
}
